/* Syncer 

This class handles the sync between iTunes and flash disk and also acts as a utility class
Maybe itunes lib specific functionality should be factored out ...

*/

import com.apple.cocoa.foundation.*;
import com.apple.cocoa.application.*;

import java.io.*;
import java.util.*;
import java.net.URL;

public class Syncer implements Runnable {

	public List<SyncListener> listeners;
	
	// Location of lib file - move to controller
	String lib = "file:///Users/grav/Music/iTunes/Albums/iTunes Library.xml";
	// Location of destination files on disk - move to controller
	String dest = "/Users/grav/testtunesync";
	// TuneSync dir
	String tsDir = "tunesync";
	
	List<String> destSongs;
	List<String> playlistSongs;
	
	NSDictionary plist;
	Map<String,NSDictionary> playlistMap; // Map playlist name -> playlist object
	
	public Syncer(){
		listeners = new LinkedList<SyncListener>();
		
		try{
			plist = getLibrary(new URL(lib));			
		} catch(Exception e){
			e.printStackTrace();
		}

		//Map playlist name -> playlist object
		playlistMap = new HashMap<String,NSDictionary>();
		NSArray playlists = (NSArray)(plist.valueForKey("Playlists"));
		for(int i=0;i<playlists.count();i++){
			NSDictionary p = (NSDictionary)(playlists.objectAtIndex(i));
			playlistMap.put((String)(p.valueForKey("Name")),p);
		}
	}
	
	public void run(){
		
	}
	
	public void addListener(SyncListener l){
		listeners.add(l);
	}
	
	public void notifyCopy(String fileName){
		for(SyncListener l : listeners){
			l.notifyCopy(fileName);
		}
	}
	
	public void notifyDelete(String fileName){
		for(SyncListener l : listeners){
			l.notifyDelete(fileName);
		}
	}
	
	public void notifyIgnore(String fileName){
		for(SyncListener l : listeners){
			l.notifyIgnore(fileName);
		}
	}
	
	public void notifyDone(){
		for(SyncListener l : listeners){
			l.notifyDone();
		}
	}
	
	public boolean inPlaylist(String song){
		song = new File(song).getName(); // Get only filename of song
		for(String plSong : playlistSongs){
			String plFileName = null;
			try{
				plFileName = new File(plSong).getName(); // Get only filename of songs in playlist
			} catch(Exception e){
				e.printStackTrace();
			}												
			if ( plFileName.equals(song) ) return true;
		}
		return false;
	}
	
	/** @return number of songs in destination 
	**/
	public int setDest(String dest){
		// TODO: Check that dest is writable
		this.dest = dest + "/" + tsDir;
		//this.dest = "/Users/grav/testts"; // DEBUG
		File f = new File(this.dest);
		if ( !f.isDirectory() ){
			// Try to create dir
			try{
				f.mkdir();
			} catch(Exception e){
				e.printStackTrace();
			}
		}
		destSongs = getDestSongs();
		return destSongs.size();
	}
	
	public int setPlaylist(String playlist){
		playlistSongs = getPlaylistSongs(playlist);
		return playlistSongs.size();
	}
	
	public void sync() {	
		// Delete songs that are not in playlist
		for(String s : destSongs){
			if(!inPlaylist(s)){
				deleteSong(s);
			} else notifyIgnore(s);
		}

		// Copy songs that are not on device
		for(String s : playlistSongs){
			if(!songInDest(s)){
				copySong(s);
			} else notifyIgnore(s);
		}
		notifyDone();
	}
	
	/**
		@precondition: destSongs are a list of songnames only
	*/
	private boolean songInDest(String song){
		return destSongs.contains(dest+"/"+new File(song).getName());
	}
	
	private void copySong(String s){
		notifyCopy(s);
		FileInputStream from = null;
		FileOutputStream to = null;
		try {
			from = new FileInputStream(s);
			to = new FileOutputStream(dest + "/" + new File(s).getName());
			byte[] buffer = new byte[4096];
			int bytesRead;
			
			while ((bytesRead = from.read(buffer)) != -1) {
				to.write(buffer, 0, bytesRead); // write
			}
		} catch(Exception e){
			e.printStackTrace();
		} finally {
			if (from != null) {
				try {
					from.close();
				} catch (IOException e) {
					e.printStackTrace();
				}				
			}
			if (to != null) {
				try {
					to.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void deleteSong(String s){
		notifyDelete(s);
		new File(s).delete();
	}
	
	private NSDictionary getLibrary(URL url){	
		return (NSDictionary)NSPropertyListSerialization.propertyListFromXMLData(new NSData(url));
	}
	
	public List<String> getPlaylists(){
		// Make a list of the playlist keys
		return new LinkedList<String>(playlistMap.keySet());
	}
	
	/** Return list of songs (with url) **/
	public List<String> getPlaylistSongs(String key){
		List<String> songs = new LinkedList<String>();

		// Get all song from plist
		NSDictionary allSongs = (NSDictionary)(plist.valueForKey("Tracks"));
		// Get filenames from songs associated with a playlist
		NSDictionary pl = playlistMap.get(key);
		NSArray plSongs = (NSArray)(pl.valueForKey("Playlist Items"));
		for(int i=0;i<plSongs.count();i++){
			// Get ID for song
			NSDictionary song = (NSDictionary)(plSongs.objectAtIndex(i));
			Long id = (Long)(song.valueForKey("Track ID"));
			// Get song obj
			song = (NSDictionary)(allSongs.valueForKey(id.toString()));
			// Make a regular filename from URL 
			String fileName = null;
			try{
				fileName = NSPathUtilities.pathFromURL(new URL((String)(song.valueForKey("Location"))));				
			} catch (Exception e){
				e.printStackTrace();
			}
			// Get location for song
			songs.add(fileName);
		}
		return songs;
	}
	
	/** Make list of songs on location */
	private List<String> getDestSongs() {
		List<String> songs = new LinkedList<String>();	
		String[] files = new File(dest).list();
		for(int i=0;i<files.length;i++){
			songs.add(dest + "/" + files[i]);
		}
		return songs;
	}
	
}
